<?php 
/* Video Conversion Class using FFMPEG, By Olivier St-Laurent
 * Version 1.2
 * 
 * You Must Install FFMPEG and configure with  --enable-libfaac --enable-shared --enable-libmp3lame --enable-gpl --enable-nonfree --enable-libvorbis --enable-pthreads --enable-postproc --enable-libx264
 * Dependencies to install : mplayer, ruby, yasm, libx264/x264, libmp3lame, libfaac/faac, libvorbis, libogg
 * To install on ubuntu : 
 *     sudo apt-get install ruby yasm mplayer x264 libmp3lame0 libmp3lame-dev faac libvorbis0a libvorbis-dev libogg0 libogg-dev; 
 *     git clone git://source.ffmpeg.org/ffmpeg.git ffmpeg; 
 *     cd ffmpeg; 
 *     ./configure --enable-libfaac --enable-shared --enable-libmp3lame --enable-gpl --enable-nonfree --enable-libvorbis --enable-pthreads --enable-libx264; 
 *     make; 
 *     sudo make install; 
 * 
 * Usage : $ffmpeg = new ffmpegConverter('INPUT_FILE_ABSOLUTE_PATH');
 *         $ffmpeg->convert('OUTPUT_FILE_ABSOLUTE_PATH', 'FFMPEG_OPTIONS');
 *    FFMPEG Options is optional, Default settings : high quality MP4 h264, lame mp3 audio (-c:v libx264 -preset fast -c:a libmp3lame)
 *    Input and Output files must not be the same.  
*/

class ffmpegConverter {
    protected $inputFile = null;
    protected $fileInfo = array();
    protected $duration = null;
    protected $frames = null;
    protected $fps = null;
    
    public $infoCallback = null;
    public $logCallback = null;
    public $percentCallback = null;
    public $completedCallback = null;
    public $errorCallback = null;
    
    public function __construct($inputFile, $callbacks=array()) {
        foreach ($callbacks as $key=>$callback) {
            $this->$key = $callback;
        }
        $this->inputFile = $inputFile;
        $this->getInfo();
    }
    
    public function getInfo() {
        if (is_file($this->inputFile)) {
            exec("ffmpeg -i ".$this->inputFile." 2>&1", $output, $success);
            if ($success) {
                $this->log("\n\n".date("Y-m-d H:i:s")." - New video Info '".$this->inputFile."' ...... \n".implode("\n", $output)."\n");
                $this->fileInfo = $output;
                $infos = implode("\n", $output);
                $durationStr = preg_replace("#^.*Duration: (\d{2}:\d{2}:\d{2}\.\d{2}),.*$#siu","$1", $infos);
                $duration = explode(':', $durationStr);
                $this->duration = ((double)$duration[0])*3600 + ((double)$duration[1])*60 + ((double)$duration[2]);
                $this->fps = (double)preg_replace("#^.*, (\d+(\.\d+)?) (fps|tbr), .*$#si","$1", $infos);
                $this->frames = $this->duration * $this->fps;
                $this->info(array(
                    'duration' => $this->duration, 
                    'fps' => $this->fps, 
                    'frames' => $this->frames, 
                    'infos' => $output,
                ));
            } else {
                $this->fileInfo = false;
                $errorMessage = "FFMPEG Error --> " . implode("\n", $output);
                $this->log("ERROR : ".$errorMessage);
                $this->error($errorMessage);
            }
        } else {
            $this->fileInfo = false;
            $errorMessage = "Input File does not exist '" . $this->inputFile . "'";
            $this->log("ERROR : ".$errorMessage);
            $this->error($errorMessage);
        }
        return $this->fileInfo;
    }
    
    public function getDuration() {
        if (!$this->fileInfo) {
            $this->getInfo();
        }
        if (!$this->fileInfo) {
            return 0;
        }
        return $this->duration;
    }
    
    public function convert($outputFile, $options='-c:v libx264 -crf 16 -preset fast -c:a libmp3lame') {
        $this->log("\nStarting Video Processing script\n\nInput file : ".$this->inputFile."\nOutput file : ".$outputFile."\nDuration : ".$this->duration."\nfps : ".$this->fps."\nframes : ".$this->frames."\n\n");
        if (is_file($outputFile)) {
            unlink($outputFile);
            $this->log("Output file already exists... Overwriting\n\n");
        }
        $handle = popen('ffmpeg -i '.$this->inputFile.' -loglevel verbose '.$options.' '.$outputFile.' 2>&1', 'r');
        $this->log("Processing...\n");
        while($line = fread($handle, 2096)) {
            $this->log($line);
            if (preg_match("#frame=\s*(\d+)\s*fps=#i", $line, $matches)) {
                $frame = (double)$matches[1];
                if ( ($percent=empty($percent)?null:$percent) != ($percent = (int)(($frame / $this->frames) * 100)) ) { // Prevent having twice the same percent
                    $this->log($percent."%\n");
                    $this->percent($percent);
                }
            }
        }
        pclose($handle);
        $this->completed();
    }
    
    /*
     * Callback functions
     */
    
    public function info($data=array()) {
        if (is_callable($this->infoCallback)) {
            call_user_func($this->infoCallback, $data);
        }
        else print_r($data);
    }
    
    public function log($message) {
        if (is_callable($this->logCallback)) {
            call_user_func($this->logCallback, $message);
        }
        else echo $message;
    }
    
    public function percent($percent) {
        if (is_callable($this->percentCallback)) {
            call_user_func($this->percentCallback, $percent);
        }
        else echo $percent;
    }
    
    public function completed() {
        if (is_callable($this->completedCallback)) {
            call_user_func($this->completedCallback);
        }
        else echo "Completed !";
    }
    
    public function error ($errorMessage) {
        if (is_callable($this->errorCallback)) {
            call_user_func($this->errorCallback, $errorMessage);
        }
        else echo "ERROR : " . $errorMessage;
    }
    
}
